package util

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"radar/nimrod/model"
	"radar/nimrod/model/header"
	"strings"
)

// ReadNimrodFile read nimrod raw polar file
func ReadNimrodFile(filePath string) (model.RadarCenter, model.NimrodVolume, error) {
	// get nimrod volume
	volume, err := readNimrod(filePath)
	if err != nil {
		return model.RadarCenter{}, model.NimrodVolume{}, fmt.Errorf("[ERROR] reading nimrod: %v", err)
	}

	// get radar center lat, lon, altitude and the length of bin
	volumeHeader := volume.Header
	radarCenter := model.RadarCenter{}
	radarCenter.Name = "Hameldon Hill"
	dateTime := BytesToUint16(volumeHeader["Identification"]["CreationTime"])
	date := []string{
		fmt.Sprintf("%4d", dateTime[0]),  // year
		fmt.Sprintf("%02d", dateTime[1]), // month
		fmt.Sprintf("%02d", dateTime[2]), // day
	}
	time := []string{
		fmt.Sprintf("%02d", dateTime[3]), // hour
		fmt.Sprintf("%02d", dateTime[4]), // minute
		"00", //second
	}
	radarCenter.DateTime = strings.Join(date, "-") + "_" + strings.Join(time, ":")
	radarCenter.Lat, radarCenter.Lon = getCenterLatLon(volumeHeader)
	radarCenter.BinLen = float64(BytesToUint16(volumeHeader["ProcessorConf"]["BinLength"])[0])
	radarCenter.Altitude = float64(BytesToUint16(volumeHeader["SiteInfo"]["SensorHeight"])[0])
	// get nimrodVolume
	nimrodVolume := model.NimrodVolume{}
	nimrodVolume.ScansNum = int(volume.ScansNum)

	scans := volume.Scans

	// read scans
	for scanNo := int16(0); scanNo < volume.ScansNum; scanNo++ {
		scan := scans[scanNo]
		nimrodScan := model.NimrodScan{}
		nimrodScan.RaysNum = int(scan.RaysNum)

		// read ray header and data
		rays := scan.Rays

		min := model.RadarGrid{Lat: 53.181954, Lon: -2.680139}
		max := model.RadarGrid{Lat: 53.990923, Lon: -1.320580}
		for rayNo := int16(0); rayNo < scan.RaysNum; rayNo++ {
			ray := rays[rayNo]
			nimrodRay := model.NimrodRay{}

			// get ray azimuth angle
			grids := make([]model.RadarGrid, len(ray.Data))
			azimuth := float64(BytesToUint16(ray.Header["AzimuthCentreAngle"])[0])
			// calculate the lat and lon of every grid
			for i := 0; i < len(ray.Data); i++ {
				gridIndex := i + 1
				grids[i].GetGridLatLon(radarCenter, gridIndex, azimuth, radarCenter.Altitude, ray.Elevation)
			}
			ray.Grids = grids
			// filter
			nimrodRay.Grids = ray.ExtractGivenRegion(min, max)
			nimrodRay.Reflectivity = ray.ExtractData(min, max)
			nimrodRay.BinsNum = len(nimrodRay.Reflectivity)
			nimrodRay.Elevation = ray.Elevation

			nimrodScan.Rays = append(nimrodScan.Rays, nimrodRay)
		}
		nimrodVolume.Scans = append(nimrodVolume.Scans, nimrodScan)
	}

	return radarCenter, nimrodVolume, nil
}

// ReadNimrod Read Nimrod file
func readNimrod(filePath string) (header.Volume, error) {
	volume := header.Volume{}
	// read file
	inputFile, inputErr := os.Open(filePath)
	defer inputFile.Close()
	if inputErr != nil {
		fmt.Fprintf(os.Stderr, "%s open err: %v\n", filePath, inputErr)
		return volume, inputErr
	}

	// read volume
	volumeHeader, volumeErr := readVolumeHeader(inputFile)
	if volumeErr != nil {
		return volume, volumeErr
	}
	volume.Header = volumeHeader

	// volume header info
	volume.ScansNum = int16(BytesToUint16(volumeHeader["ProcessorConf"]["ScansNum"])[0])
	// read scans
	for scanNo := int16(0); scanNo < volume.ScansNum; scanNo++ {
		scan := header.Scan{}
		// read scan header
		scanHeader, scanErr := readScanHeader(inputFile)
		if scanErr != nil {
			return volume, scanErr
		}
		scan.Header = scanHeader

		// read ray header and data
		scan.RaysNum = int16(BytesToUint16(scanHeader["RaysNum"])[0])
		for rayNo := int16(0); rayNo < scan.RaysNum; rayNo++ {
			// read ray
			ray := header.Ray{}
			rayHeader, rayErr := readRayHeader(inputFile)
			if rayErr != nil {
				return volume, rayErr
			}
			ray.Header = rayHeader
			ray.DataElementSize = int16(BytesToUint16(volume.Header["DataStorge"]["ElementBytesNum"])[0])
			ray.BinsNum = int16(BytesToUint16(scan.Header["BinsNum"])[0])
			ray.Elevation = float64(BytesToUint16(ray.Header["ElevationAngle"])[0]) / 100
			dataSize := ray.DataElementSize * ray.BinsNum
			// read data
			dat, err := readData(inputFile, dataSize)
			if err != nil {
				return volume, err
			}
			datUint16 := BytesToUint16(dat)
			refs := []uint16{}
			for _, item := range datUint16 {
				ref := ExtractReflectivity(item)
				refs = append(refs, ref)
			}
			ray.Data = refs
			scan.Rays = append(scan.Rays, ray)
		}
		volume.Scans = append(volume.Scans, scan)
	}
	return volume, nil
}

func readVolumeHeader(f *os.File) (map[string](map[string][]byte), error) {
	volumeHeader := header.InitVolumeHeader()
	for _, subHeaderName := range header.VolumeKey {
		for _, subKey := range header.SubHeader[subHeaderName] {
			_, err := f.Read(volumeHeader[subHeaderName][subKey])
			if err != nil {
				fmt.Fprintf(os.Stderr, "Reading file %s, volume header error occured: %v", f.Name(), err)
				return nil, err
			}
		}
	}
	return volumeHeader, nil
}

func readScanHeader(f *os.File) (map[string][]byte, error) {
	scanHeader := header.InitScanHeader()
	for _, key := range header.ScanKey {
		_, err := f.Read(scanHeader[key])
		if err != nil {
			fmt.Fprintf(os.Stderr, "Reading file %s, scan header error occured: %v", f.Name(), err)
			return nil, err
		}
	}
	return scanHeader, nil
}

func readRayHeader(f *os.File) (map[string][]byte, error) {
	rayHeader := header.InitRayHeader()
	for _, key := range header.RayKey {
		_, err := f.Read(rayHeader[key])
		if err != nil {
			fmt.Fprintf(os.Stderr, "Reading file %s, ray header error occured: %v", f.Name(), err)
			return nil, err
		}
	}
	return rayHeader, nil
}

func readData(f *os.File, dataSize int16) ([]byte, error) {
	if dataSize < 0 {
		return nil, errors.New("invalid data size")
	}
	dat := make([]byte, dataSize)
	_, err := f.Read(dat)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Reading file %s, data error occured: %v", f.Name(), err)
		return nil, err
	}

	return dat, nil
}

// GetCenterLatLon returns latitude and longitude of center
func getCenterLatLon(volumeHeader map[string](map[string][]byte)) (float64, float64) {
	latBytes := BytesToInt16(volumeHeader["SiteInfo"]["RadarSiteLat"])
	lonBytes := BytesToInt16(volumeHeader["SiteInfo"]["RadarSiteLon"])
	lat := degreeToDecimal(latBytes)
	lon := degreeToDecimal(lonBytes)
	return lat, lon
}

func degreeToDecimal(degree []int16) float64 {
	return float64(degree[0]) + float64(degree[1])/60 + float64(degree[2])/3600
}

// ReadFile and return its content
func ReadFile(in string) ([]byte, error) {
	dat, err := ioutil.ReadFile(in)
	if err != nil {
		return nil, err
	}
	return dat, nil
}
